package com.gitee.chessboard.scan.discern;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;

/**
 * 识别器的抽象实现
 *
 * @author 敲代码的旺财
 * @date 2021/12/10 14:12
 */
public abstract class AbstractDiscernPortion implements DiscernPortion {

    /**
     * 识别结果
     */
    Set<Class<?>> discernResults;

    DiscernPortion next;

    /**
     * 目标描述对象
     */
    Object targetDescribe;

    public AbstractDiscernPortion() {
        this.discernResults = new HashSet<>();
    }

    /**
     * 对目标进行描述，用于甄别是否满足目标要求
     *
     * @param obj 目标对象特征描述
     */
    @Override
    public void targetDescribe(Object obj) {
        this.targetDescribe = obj;
    }

    /**
     * 进行处理
     *
     * @param obj 需要识别的对象
     * @return 是否为识别目标
     */
    @Override
    public Class<?> handle(Object obj) {
        //Obj是怎么类型的，只会是URL或者是ClassReader
        ClassReader cr;
        if (obj instanceof URL) {
            try (InputStream is = ((URL) obj).openStream()) {
                cr = new ClassReader(is);
            } catch (IOException e) {
                throw new RuntimeException("读取资源失败", e);
            }
        } else if (obj instanceof ClassReader) {
            cr = (ClassReader) obj;
        } else {
            throw new RuntimeException("无法识别的数据类型");
        }
        boolean discernFlag = discern(cr);
        if (discernFlag) {
            //识别为目标
            ClassWriter cw = new ClassWriter(cr, 0);
            //对字节进行修改
            ClassVisitor cv = beforeLoadHandle(cw);
            //读取修改的字节码
            cr.accept(cv, 0);

            cr = new ClassReader(cw.toByteArray());
        }

        Class<?> clazz;
        //判断是否为最后一个节点
        if (next == null) {
            //是最后一个节点，需要加载这个对象
            ClassWriter cw = new ClassWriter(cr, 0);
            cr.accept(cw,0);
            String className = cr.getClassName().replace("/", ".");
            ClassLoader classLoader = AbstractDiscernPortion.class.getClassLoader();
            byte[] b = cw.toByteArray();
            try {
                Method defineClass = ClassLoader.class
                        .getDeclaredMethod("defineClass", String.class, byte[].class,int.class,int.class);
                defineClass.setAccessible(true);
                clazz = (Class<?>) defineClass.invoke(classLoader
                        , className
                        , b
                        ,0
                        ,b.length);
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                throw new RuntimeException(e);
            }
        } else {
            //不是最后一个节点就向下传递
            clazz = next.handle(cr);
        }

        if (discernFlag) {
            //如果是识别目标需要添加到缓存中
            discernResults.add(clazz);
        }
        return clazz;
    }

    /**
     * 设置后一个节点
     *
     * @param discernPortion 识别器
     */
    @Override
    public void setNext(DiscernPortion discernPortion) {
        this.next = discernPortion;
    }


    /**
     * 甄别目标
     *
     * @param cr 需要识别的对象
     * @return 是否为识别目标
     */
    public abstract boolean discern(ClassReader cr);

    /**
     * 在加载前进行处理
     *
     * @param cw 需要识别的对象
     * @return 处理后的对象
     */
    public abstract ClassVisitor beforeLoadHandle(ClassWriter cw);

    /**
     * 获取结果集
     *
     * @return 结果集
     */
    public Set<Class<?>> getDiscernResults() {
        return discernResults;
    }
}
